package edu.ntu.cloudroid.client;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;

import edu.ntu.cloudroid.model.IDs;
import edu.ntu.cloudroid.model.UserData;
import edu.ntu.cloudroid.model.data.LocationInfo;
import edu.ntu.cloudroid.model.data.TraceInfo;
import edu.ntu.cloudroid.model.data.VitalSignData;
import edu.ntu.cloudroid.model.data.VitalSignInfo;
import edu.ntu.cloudroid.model.friend.FriendInfo;
import edu.ntu.cloudroid.model.friend.GroupInfo;
import edu.ntu.cloudroid.model.policy.AbstractPrivacyPreference;
import edu.ntu.cloudroid.model.policy.PolicyInfo;
import edu.ntu.cloudroid.model.policy.SimpleSlidingWindow;
import edu.ntu.cloudroid.model.profile.ProfileInfo;
import edu.ntu.cloudroid.nongui.com.LocationRequestWithInputs;

public class Generator {
	public static String userPrefix = "user";
	public static String groupPrefix = "group";
	public static String ALL = "all";
	public static String GROUP = "group";

	public static Set<LocationObject> locFromFile=null; 
	public static List<FriendInfo> friendList = new ArrayList<FriendInfo>();
	
	public static List<UserData> generateProfiles(int dataType, int n) {
		List<UserData> list = new ArrayList<UserData>();
		String email;
		for (int i = 0; i < n; i++) {
			email = userPrefix + "_" + i + "@gmail.com";
			list.add(new ProfileInfo(email, dataType, 0));
		}
		return list;
	}

	public static List<UserData> generateDeleteProfiles(int dataType, int n) {
		List<UserData> list = new ArrayList<UserData>();
		String email;
		for (int i = 0; i < n; i++) {
			email = "null "+userPrefix + "_" + i + "@gmail.com";
			list.add(new ProfileInfo(email, dataType, 0));
		}
		return list;
	}
	
	public static List<String> generateFunctions(int n) {
		// List<PolicyInfo> list = new ArrayList<PolicyInfo>();
		List<String> list = new ArrayList<String>();
		Random rand = new Random(100);
		// String username = "user_0@gmail.com";
		// //String.format("%s_%d@gmail.com", userPrefix, i);

		for (int i = 0; i < n; i++) {
			// boolean filter = rand.nextBoolean();
			// String pref = "," + (filter ? 0 : 4) + "," + ALL + ",";
			// if (filter) {
			String pref = ",0,all,";
			pref += AbstractPrivacyPreference.VALUECONSTRAINT + ",";
			switch (rand.nextInt(3)) {
			case 0:
				pref += AbstractPrivacyPreference.EQUAL;
				break;
			case 1:
				pref += AbstractPrivacyPreference.GEQUAL;
				break;
			case 2:
				pref += AbstractPrivacyPreference.LEQUAL;
				break;
			}
			if (rand.nextBoolean())
				pref += "," + VitalSignData.HEART_RATE;
			else
				pref += "," + VitalSignData.BLOOD_PRESSURE;
			pref += "," + rand.nextDouble() * 100;
			pref += "," + false;
			/*
			 * } else { switch (rand.nextInt(4)) { case 0: pref +=
			 * IDs.StatFunctionID.CountFunction.ordinal(); break; case 1: pref
			 * += IDs.StatFunctionID.MaxFunction.ordinal(); break; case 2: pref
			 * += IDs.StatFunctionID.MinFunction.ordinal(); break; case 3: pref
			 * += IDs.StatFunctionID.AverageFunction.ordinal(); break; } pref +=
			 * String.format(",%d,", (rand.nextBoolean() ?
			 * VitalSignData.HEART_RATE : VitalSignData.BLOOD_PRESSURE)); pref
			 * += false; }
			 */
			// System.out.println(pref);

			// PolicyInfo p = new PolicyInfo(username, pref.split(","));
			list.add(pref);
		}

		return list;
	}

	public static void main(String[] args) throws Exception {
		//generateFriendsFromFile(args[0]);
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
		Date date = sdf.parse(PrepareData.DEFAULT_DATE);
		// generateVitalSignDataFromFile(args[0], 5000, date, 10, 10);
		generateLocationDataFromFile(args[0], 5000, date, 10, 10);
		generateLocationRequestWithInputsFromFile(5000, new Random(100), 1000);
	}

	public static List<UserData> generateDeletePolicy(int type, int n, boolean isLatest){
		String user, email, pref;
		List<UserData> list = new ArrayList<UserData>();
		for (int i = 0; i < n; i++) {
			user = userPrefix + "_" + i;
			email = "null "+user + "@gmail.com";
			switch (type) {
			case 0:
				pref = email + ",0," + ALL + ","
						+ AbstractPrivacyPreference.VALUECONSTRAINT + ","
						+ AbstractPrivacyPreference.GEQUAL + ",0,20,"
						+ new Boolean(isLatest).booleanValue();
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 1:
				// granuality poliy, for all users who are friends
				// exact value
				pref = email + ",1," + ALL + ",0,0,"
						+ new Boolean(isLatest).booleanValue();
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 2:
				pref = email + ",2," + ALL + ",1,1,0,20,"
						+ new Boolean(isLatest).booleanValue();
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 3:
				// sliding window type, average over the window
				// <email> <type> <all/group/user> <average|frequent> (<size>
				// <starting> <advance>) or (<size>)
				pref = email + ",3," + ALL + ","
						+ IDs.StatFunctionID.AverageFunction.ordinal()
						+ ",0,5,2011-01-01 00:00,3";
				/*
				 * pref = email + ",3," + ALL + "," + PrivacyPreference.AVERAGE
				 * + ",5," + new Boolean(isLatest).booleanValue();
				 */
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 4:
				// simple stats
				// <email> <type> <all/group/user> <function> <column index>
				// <isInputRequired>
				pref = email + ",4," + ALL + ","
						+ IDs.StatFunctionID.MinFunction.ordinal() + ",0,"
						+ new Boolean(false).booleanValue();
				/*
				 * pref = email + ",3," + ALL + "," + PrivacyPreference.AVERAGE
				 * + ",5," + new Boolean(isLatest).booleanValue();
				 */
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			}
		}
		return list;
	}
	
	public static List<UserData> generatePolicies(int type, int n,
			boolean isLatest) {
		String user, email, pref;
		List<UserData> list = new ArrayList<UserData>();
		for (int i = 0; i < n; i++) {
			user = userPrefix + "_" + i;
			email = user + "@gmail.com";
			switch (type) {
			case 0:
				pref = email + ",0," + ALL + ","
						+ AbstractPrivacyPreference.VALUECONSTRAINT + ","
						+ AbstractPrivacyPreference.GEQUAL + ",0,20,"
						+ new Boolean(isLatest).booleanValue();
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 1:
				// granuality poliy, for all users who are friends
				// exact value
				pref = email + ",1," + ALL + ",0,0,"
						+ new Boolean(isLatest).booleanValue();
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 2:
				pref = email + ",2," + ALL + ",1,1,0,20,"
						+ new Boolean(isLatest).booleanValue();
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 3:
				// sliding window type, average over the window
				// <email> <type> <all/group/user> <average|frequent> (<size>
				// <starting> <advance>) or (<size>)
				pref = email + ",3," + ALL + ","
						+ IDs.StatFunctionID.AverageFunction.ordinal()
						+ ",0,5,2011-01-01 00:00,3";
				/*
				 * pref = email + ",3," + ALL + "," + PrivacyPreference.AVERAGE
				 * + ",5," + new Boolean(isLatest).booleanValue();
				 */
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			case 4:
				// simple stats
				// <email> <type> <all/group/user> <function> <column index>
				// <isInputRequired>
				pref = email + ",4," + ALL + ","
						+ IDs.StatFunctionID.MinFunction.ordinal() + ",0,"
						+ new Boolean(false).booleanValue();
				/*
				 * pref = email + ",3," + ALL + "," + PrivacyPreference.AVERAGE
				 * + ",5," + new Boolean(isLatest).booleanValue();
				 */
				list.add(new PolicyInfo(email, pref.split(",")));
				break;
			}
		}
		return list;
	}

	public static List<UserData> generateFriendsFromFile(String file)
			throws Exception {
		BufferedReader reader = new BufferedReader(new FileReader(file));
		String line;
		String[] friend;
		int idx0, idx1;
		List<UserData> list = new ArrayList<UserData>();
		FriendInfo fi;
		while ((line = reader.readLine()) != null) {
			friend = line.split("\t| ");
			fi = new FriendInfo(userPrefix + "_" + friend[0] + "@gmail.com",
					userPrefix + "_" + friend[1] + "@gmail.com", FriendInfo.ADD);
			list.add(fi);
			friendList.add(fi);
			friendList.add(new FriendInfo(fi.getUser2(), fi.getUser1(), FriendInfo.ADD));
		}
		return list;
	}

	public static List<UserData> generateDeleteFriends(int nUsers) {
		List<UserData> list = new ArrayList<UserData>();
	
		for (int i = 0; i < nUsers; i++) {			
			list.add(new FriendInfo("null user_"+i+"@gmail.com", "null", FriendInfo.ADD));
		}
		return list;
	}
	
	public static List<FriendInfo> generateFriends(int nUsers, int destinator) {
		List<FriendInfo> list = new ArrayList<FriendInfo>();

		String username = String.format("%s_%d@gmail.com", userPrefix,
				destinator);
		for (int i = 0; i < nUsers; i++) {
			if (destinator == i)
				continue;
			list.add(new FriendInfo(username, String.format("%s_%d@gmail.com",
					userPrefix, i), FriendInfo.ADD));
		}

		return list;
	}

	public static List<FriendInfo> generateFriends(int nUsers) {
		List<FriendInfo> list = new ArrayList<FriendInfo>();
		Random r = new Random(100);
		int nFriends;

		for (int i = 0; i < nUsers / 2; i++) {
			// a random number of friend
			nFriends = 0;
			while (nFriends == 0)
				nFriends = r.nextInt(nUsers / 2);
			// generate a random nFriends index
			HashSet<Integer> friendSet = new HashSet<Integer>();
			while (friendSet.size() < nFriends) {
				int j = r.nextInt(nUsers);
				if (j > i && !friendSet.contains(j)) {
					friendSet.add(j);
					list.add(new FriendInfo(
							userPrefix + "_" + i + "@gmail.com", userPrefix
									+ "_" + j + "@gmail.com", FriendInfo.ADD));
				}
			}
		}
		return list;
	}

	// Generate one-friend group
	public static List<GroupInfo> generateGroup(int userId,
			List<String> friendList) {
		List<GroupInfo> list = new ArrayList<GroupInfo>();
		int i = 0;
		for (String s : friendList) {
			Set<String> members = new HashSet<String>();
			members.add(s);
			list.add(new GroupInfo("user_" + userId + "@gmail.com", "group_"
					+ i, members, GroupInfo.NEW));
			i++;
		}
		return list;
	}

	public static PolicyInfo generateGroupPolicies(int type, int userId,
			int groupId, boolean isLatest) {
		String user, email, pref;
		user = userPrefix + "_" + userId;
		email = user + "@gmail.com";
		switch (type) {
		case 0:
			pref = email + ",0," + GROUP + "," + groupId + ","
					+ +AbstractPrivacyPreference.VALUECONSTRAINT + ","
					+ AbstractPrivacyPreference.GEQUAL + ",0,20,"
					+ new Boolean(isLatest).booleanValue();
			return new PolicyInfo(email, pref.split(","));
		case 1:
			// granuality poliy, for all users who are friends
			// exact value
			pref = email + ",1," + GROUP + "," + groupId + ",1,0,"
					+ new Boolean(isLatest).booleanValue();
			return new PolicyInfo(email, pref.split(","));
		case 2:
			pref = email + ",2," + GROUP + "," + groupId + ","
					+ IDs.SimilarityFunctionID.VitalSignSimilar.ordinal()
					+ ",0,1,20," + new Boolean(isLatest).booleanValue();
			return new PolicyInfo(email, pref.split(","));
		case 3:
			// sliding window type, average over the window
			// <email> <type> <all/group/user> <average|frequent> (<size>
			// <starting> <advance>) or (<size>)
			pref = email + ",3," + GROUP + "," + groupId + ","
					+ IDs.StatFunctionID.MaxFunction.ordinal()
					+ ",0,5,2011-01-01 00:00,3";
			return new PolicyInfo(email, pref.split(","));
		case 4:
			// simple stats
			// <email> <type> <all/group/user> <function> <column index>
			// <isInputRequired>
			pref = email + ",4," + ALL + ","
					+ IDs.StatFunctionID.MostFrequentLocFunction.ordinal()
					+ ",0," + new Boolean(true).booleanValue();
			/*
			 * pref = email + ",3," + ALL + "," + PrivacyPreference.AVERAGE +
			 * ",5," + new Boolean(isLatest).booleanValue();
			 */
			return new PolicyInfo(email, pref.split(","));
		}
		return null;
	}

	public static List<UserData> generateLocationSimilarityPolicy(
			int n) {
		String user, email, pref;
		List<UserData> list = new ArrayList<UserData>();
		for (int i=0; i<n; i++){
			user = userPrefix+"_"+i+"@gmail.com";
			pref = user + ",2," + ALL + ",0,1,0,20,"
					+ new Boolean(false).booleanValue();
			list.add(new PolicyInfo(user, pref.split(",")));
		}
		return list;
	}
	
	public static List<LocationRequestWithInputs> generateLocationRequestWithInputsFromFile(
			int nUsers, Random rand, int nRequests) {
		List<LocationRequestWithInputs> list = new ArrayList<LocationRequestWithInputs>();
		if (locFromFile==null){
			System.err
					.println("Must READ the location data file first, call the generateLocationDataFromFile method before this");
			System.exit(1);
		}
		int max = locFromFile.size();
		int idx, locIdx;
		LocationObject lo;
		LocationRequestWithInputs lrwi = null;
		for (int i=0; i<nRequests; i++){
			idx = rand.nextInt(nUsers);
			lo = (LocationObject)locFromFile.toArray()[rand.nextInt(max)];
			lrwi = new LocationRequestWithInputs(userPrefix + "_" + idx
					+ "@gmail.com", lo.longt, lo.lat, "Random");
			list.add(lrwi);			
		}
		return list;
	}
	
	public static List<UserData> generateLocationDataFromFile(
			String fileName, int nUsers, Date startingDate, int interval,
			int nDataPerUsers) throws Exception {
		BufferedReader reader = new BufferedReader(new FileReader(fileName));
		String line, name;
		String[] data;
		Calendar cal = Calendar.getInstance();
		LocationInfo fi = null;
		List<UserData> list = new ArrayList<UserData>();
		locFromFile = new HashSet<LocationObject>();
		LocationObject lo;
		int oldId = -1;
		while ((line = reader.readLine()) != null) {
			data = line.split("\t| ");
			name = userPrefix + "_" + data[0] + "@gmail.com";
			if (new Integer(data[0]).intValue()!=oldId){
				cal.setTime(startingDate);
				oldId = new Integer(data[0]).intValue();
			}
			fi = new LocationInfo(name, new Double(data[3]).doubleValue(),
					new Double(data[2]).doubleValue(), data[4], cal.getTime());
			cal.add(Calendar.MINUTE, interval);
			list.add(fi);
			lo = new LocationObject(fi.getApproxLongitude(),
					fi.getApproxLatitude());
			if (!locFromFile.contains(lo))
				locFromFile.add(lo);			
		}
		return list;
	}

	// interval is in minute
	public static List<LocationInfo> generateLocationData(int nUsers,
			Date startingDate, int interval, int nDataPerUsers) {
		String name;
		Random rand = new Random(100);
		Calendar cal = Calendar.getInstance();
		LocationInfo fi;
		List<LocationInfo> list = new ArrayList<LocationInfo>();
		for (int u = 0; u < nUsers; u++) {
			name = userPrefix + "_" + u + "@gmail.com";
			cal.setTime(startingDate);
			for (int i = 0; i < nDataPerUsers; i++) {
				cal.add(Calendar.MINUTE, interval);
				fi = new LocationInfo(name, 103.681959, 1.342966, "RandomLoc",
						cal.getTime());
				/*
				 * fi = new LocationInfo(name, rand.nextDouble() * 200,
				 * rand.nextDouble() * 200, "",cal.getTime());
				 */
				list.add(fi);
			}
		}
		return list;
	}

	// read from the vital sign sample data, choose a random starting point in
	// the sample and take nDataPerUsers
	// consecutive data type. The blood pressure value changed to oxygen
	// volumne.
	public static List<UserData> generateVitalSignDataFromFile(
			String fileName, int nUsers, Date startingDate, int interval,
			int nDataPerUsers) throws Exception {
		BufferedReader reader = new BufferedReader(new FileReader(fileName));
		List<Double> hr = new ArrayList<Double>();
		List<Double> vol = new ArrayList<Double>();
		String line;
		String[] data;
		while ((line = reader.readLine()) != null) {
			data = line.split(" ");
			hr.add(new Double(data[0]));
			vol.add(new Double(data[2]));
		}

		List<UserData> list = new ArrayList<UserData>();
		Calendar cal = Calendar.getInstance();
		String name;
		Random rand = new Random(100);
		int startingIdx;
		VitalSignInfo vi = null;
		int maxSize = hr.size();
		for (int i = 0; i < nUsers; i++) {
			name = userPrefix + "_" + i + "@gmail.com";
			cal.setTime(startingDate);
			startingIdx = rand.nextInt(maxSize);
			for (int j = 0; j < nDataPerUsers; j++) {
				cal.add(Calendar.MINUTE, interval);
				vi = new VitalSignInfo(name, cal.getTime(),
						hr.get((startingIdx + j) % maxSize),
						vol.get((startingIdx + j) % maxSize));
				list.add(vi);
				if (i == (nUsers - 1)) {
					System.out.println("last hr = " + vi.getHeartRate()
							+ ", last vol = " + vi.getOxygenVolume());
				}
			}
		}
		System.out.println("last hr = " + vi.getHeartRate() + ", last vol = "
				+ vi.getOxygenVolume());
		return list;
	}

	public static List<VitalSignInfo> generateVitalSignData(int nUsers,
			Date startingDate, int interval, int nDataPerUsers) {
		String name;
		Random rand = new Random(100);
		Calendar cal = Calendar.getInstance();
		VitalSignInfo vi;
		List<VitalSignInfo> list = new ArrayList<VitalSignInfo>();
		for (int u = 0; u < nUsers; u++) {
			name = userPrefix + "_" + u + "@gmail.com";
			cal.setTime(startingDate);
			for (int i = 0; i < nDataPerUsers; i++) {
				cal.add(Calendar.MINUTE, interval);
				vi = new VitalSignInfo(name, cal.getTime(),
						rand.nextDouble() * 100, rand.nextDouble() * 100);
				list.add(vi);
			}
		}
		return list;
	}

	// assume that users share the same number of landmarks. The landmarks id:
	// 0,1,..99
	public static List<TraceInfo> generateTraceData(int nUsers,
			Date startingDate, int interval, int nDataPerUsers, int nLandmarks) {
		String name;
		Random rand = new Random(100);
		Calendar cal = Calendar.getInstance();
		TraceInfo ti;
		List<TraceInfo> list = new ArrayList<TraceInfo>();
		for (int u = 0; u < nUsers; u++) {
			name = userPrefix + "_" + u + "@gmail.com";
			cal.setTime(startingDate);
			for (int i = 0; i < nDataPerUsers; i++) {
				cal.add(Calendar.MINUTE, interval);
				List<Integer> lm = new ArrayList<Integer>();
				for (int j = 0; j < nLandmarks; j++) {
					lm.add(rand.nextInt(100));
				}
				ti = new TraceInfo(name, cal.getTime(), lm,
						rand.nextDouble() * 100, rand.nextDouble() * 100,
						rand.nextDouble() * 100);
				list.add(ti);
			}
		}
		return list;
	}
}

class LocationObject{
	double longt, lat;
	
	public LocationObject(double lo, double la){
		this.longt = lo;
		this.lat = la;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		long temp;
		temp = Double.doubleToLongBits(lat);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		temp = Double.doubleToLongBits(longt);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		LocationObject other = (LocationObject) obj;
		if (Double.doubleToLongBits(lat) != Double.doubleToLongBits(other.lat))
			return false;
		if (Double.doubleToLongBits(longt) != Double
				.doubleToLongBits(other.longt))
			return false;
		return true;
	}
	
	
}
